package escalonamento;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import model.Processo;

/**
 *  
 * @author João Gonçalves
 * @author Eventon Silva
 */
public class EscalonamentoFCFS implements InterfaceEscalonamento {

    /**
     * Método responsável pelo escalonamento First Come, First Served (FCFS) e
     * pela impressão dos dados desse algoritmo.
     *
     * @param processos Lista de processos
     */
    @Override
    public void escalonamento(ArrayList<Processo> processos) {

        // Tempo decorrido
        int tempoDecorrido = 0;
        // Tempo médio de execução
        double tempoMedioExecucao = 0;
        // Tempo médio de espera de um processo
        double tempoMedioEspera = 0;
        // Quantidade de processos
        int quantProcessos = processos.size();
        // Troca de contexto
        int trocaContexto = 0;

        // Processo em execução
        Processo processoExecucao = null;

        /*
         *  Lista com os processos em execução e o com
         *  os dados recolhidos, respectivamente.
         */
        Queue<Processo> listaProcesso = new LinkedList<>();
        Queue<Processo> listaProcessoDados = new LinkedList<>();

        System.out.printf("tempo ");

        /*
         * Enumera os processos de P1 a PN, onde N
         * é o número total de processos.
         */
        for (int i = 1; i <= quantProcessos; i++) {
            System.out.printf("P%2s%2s", String.valueOf(i), "");
        }

        // Quebra de linha
        System.out.println();

        /*
         * Loop principal, responsável por processar todos
         * os dados até a obtenção das informações necessários
         * para apresentar os dados sobre os processos.
         * 
         * A condição de parada é a lista de processos executados
         * ter o mesmo tamanho da de entrada, logo todos eles 
         * foram executados.
         */
        while ( listaProcessoDados.size() != processos.size() ){

            /*
             * Se o processo entra no instante X, então
             * ele será adicionado a fila de processos
             * para execução
             */
            for (Processo processo : processos) {
                if (processo.getDataCriacao() == tempoDecorrido) {
                    listaProcesso.add(processo);
                }
            }

            /*
             * Se nenhum processo estiver em execução, irá
             * ser colocado o topo da fila de processos em
             * estado ativo, ou seja, o processo em execução
             * no momento.
             */
            if ((!listaProcesso.isEmpty()) && (listaProcesso.peek().getStatus() == 0)) {

                processoExecucao = listaProcesso.peek();
                processoExecucao.setStatus(1);
                processoExecucao.tempo();
                trocaContexto++; // Nova troca de contexto
            }

            System.out.printf("%2s-%2s ", String.valueOf(tempoDecorrido), (tempoDecorrido + 1));

            /*
             * Imprime o estado do processo, de acordo com,
             * se ele se encontra em execução (ativo ou espera)
             * ou não está em execução.
             */
            for (int i = 0; i < quantProcessos; i++) {

                if (!listaProcesso.contains(processos.get(i))) {
                    System.out.printf("%2s%3s", " ", "");
                } else {

                    if (processos.get(i).equals(processoExecucao)) {
                        System.out.printf("%2s%3s", "#", "");
                    } else {
                        System.out.printf("%2s%3s", "-", "");
                    }
                }
            }

            System.out.println();

            /*
             * Verifica se o processo ainda tem tempo para ser
             * executado, se tiver continuar diminuindo o tempo.
             * Senão, ele finaliza o projeto e guarda os dados sobre o processo.
             */
            if (processoExecucao != null && processoExecucao.getDuracaoRestante() > 0) {
                processoExecucao.tempo();
            } else {
                
                processoExecucao = null;

                if (listaProcesso.peek() != null) {
                    listaProcesso.peek().setTermino(tempoDecorrido + 1);
                    listaProcessoDados.add(listaProcesso.poll());
                }

            }

            // Contabiliza o tempo decorrido de execução
            tempoDecorrido += 1;
        }

        /*
         * Calcula o somatório dos tempos gastos na
         * execução e espera pelos processos.
         */
        for (Processo processo : listaProcessoDados) {
            tempoMedioExecucao += processo.getTermino() - processo.getDataCriacao();
            tempoMedioEspera += processo.getTermino() - processo.getDuracao() - processo.getDataCriacao();
        }

        /*
         * Calcula os tempos médios de execução e espera de acordo
         * com a quantidade de processos.
         */
        tempoMedioExecucao = tempoMedioExecucao / quantProcessos;
        tempoMedioEspera = tempoMedioEspera / quantProcessos;

        System.out.println(
                "\nTempo médio execução: " + tempoMedioExecucao + "s");
        System.out.println(
                "Tempo médio de espera: " + tempoMedioEspera + "s");
        System.out.println(
                "Número de troca de contexto: " + trocaContexto);
    }
}